/*
    YmsgIRC - Program enabling YMSG chat clients to use IRC
    Copyright (C) 2011 Crash Daemonicus ( crashenator -at- gmail.com )

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#include "IRC.h"

void IrcCommand::init(int mode)
{
    //dbg_head("IrcCommand::init(int mode)");
    if(mode==0 || mode==1)
    {
        //dbg_head("str");
        str="";
        //dbg_foot(0);
    }
    if(mode==0 || mode==2)
    {
        //dbg_head("components");
		parts.clear();
        origin="";
        type="";
        destination="";
        content="";
            subcontent="";
            text="";
        //dbg_foot(0);
    }
    //dbg_foot(0);
}



void IrcCommand::shortorigin()
{
    //dbg_head("IrcCommand::shortorigin()");
    if(origin!="")
    {
        size_t excl=origin.find('!');
        if(excl==-1) return dbg_foot("noexcl");
        origin = origin.substr(0,excl);
    }
    //dbg_foot(0);
}
void IrcCommand::process()
{
    //debate whether it would be more efficient to refactor with strtok()
    using namespace std;
    dbg_head("IrcCommand::process()");
    init(2);
    singlecontent=true;
    string s=str;

	if(s[0]==':') s=s.substr(1);//strip preceeding `:`?

	vector<string> sSplitText;
	StringTokenize(s,sSplitText,":");
	sSplitText.push_back("");

	text=sSplitText[1];

	StringTokenize(sSplitText[0],parts," ");
	parts.push_back(":" + text);  // part part part :pa rt


	int i=0;
	//for(vector<string>::iterator it=parts.begin();it!=parts.end();it++)
	foreach_vector(string,it,parts)
	{
		if(i==0) origin       =*it;
		if(i==1) type         =*it;
		if(i==2) destination  =*it;
		
		if(i>1) content    += *it +" ";
		if(i>2) subcontent += *it +" ";

		i++;
	}
    dbg_foot(0);
}
void IrcCommand::assemble(bool client)
{
    dbg_head("IrcCommand::assemble(bool client)");
    using namespace std;
    str="";
    string s="";
    if(!client && origin!=""){ s+=":"; s+=origin; s+=" "; }
    s+=type;
    if(singlecontent)
    {
        dbg_head("singlecontent");
        if(destination!=""){
            dbgline("d=");dbg(destination);
            s+=" "; s+=destination;
        }
        if(text!=""){
            dbgline("t=");dbg(text); 
            s+=" :";s+=text;
        }
        dbg_foot(0);
    }else{
        dbg_head("nonsinglecontent");
        dbgline("s=");dbg(s);
        if(single_incOrigin && origin!=""){
            dbgline("o=");dbg(origin);
            s+=" "; s+=origin;
        }
        if(content!=""){ 
            dbgline("c=");dbg(content);
            s+=" "; s+=content; 
        }
        dbg_foot(0);
    }
    str=s;
    dbg_foot(0);
}
IrcCommand::~IrcCommand(){

}
IrcCommand::IrcCommand(){
    init();
    singlecontent=true;single_incOrigin=false;
}
IrcCommand::IrcCommand(const string& command)
{
    dbg_head("IrcCommand(string command)");
    init();
    str=command;
    process();
    dbg_foot(0);
}
IrcCommand::IrcCommand(const string& src,const string& dst,const string& typ,const string& cont,bool singlecont, bool incOrig){
    
    dbg_head("IrcCommand(string,string, string, string,bool, bool)");
    dbgline("IrcCommand content ");
    dbg(src);
    dbg(" | ");
    dbg(dst);
    dbg(" | ");
    dbg(typ);
    dbg(" | ");
    dbg(cont);
    dbg("\r\n");
    

    
    init();
    singlecontent=singlecont;
    single_incOrigin=incOrig;
    
    origin=src;
    destination=dst;
    type=typ;
    
    dbgline("{{ CONT ");
    dbg(cont);
    dbg(" }}\r\n");
    text=cont;
    dbgline("{{ TEXT ");
    dbg(text);
    dbg(" }}\r\n");
    content=cont;
    
    
    
    assemble(true);
    process();
    dbg_foot(0);
}



void IrcCommand::debug()
{
    dbg_head("IrcCommand::debug()");
    //dbgline("dummy (no content yet)");
    
    //puts(str);
    //assemble(false);
    puts(str);
    printf("Type %s\n",type.c_str());
    printf("Origin %s\n",origin.c_str());
    printf("Destination %s\n",destination.c_str());
    printf("Content %s\n",content.c_str());
    printf("Subcontent %s\n",subcontent.c_str());
    printf("Text %s\n",text.c_str());
    dbg_foot(0);
}

void IRC::init()
{
    dbg_head("IRC::init()");
    name="IRC";
    username="";
    password="melzar";
    connection.setBufLimit(512);
    connection.buf_nullterminated=true;
    
    dbg_foot(0);
}
IRC::IRC(){
    dbg_head("IRC()");
    IRC::init();
    dbg_foot(0);
}
bool IRC::sendMsg(const IrcCommand& ic)
{
    bool ret=connection.put(ic.str + "\r\n");
    puts(string(">>> IRC: ")+ic.str + "\r\n");
    checkErrors(0);
    return ret;
}


void IRC::stepIncoming(IrcCommand& ic)
{
    dbg_do(printf("\nIC_TYPE={%s}\n",ic.type.c_str()));
    int iType=atoi(ic.type.c_str());
    if(iType==001)
    {
        NwEUsername(ic.destination);
        
        char text[512];
        sprintf(text,"*** You are now logged into IRC as '%s'.",username.c_str());
        puts(text);
        //***if( !sendClientMsg("",ymsg->username,text,false)) return 2;
        NwEMsg(NW_RESERVED_USERNAME,"",text);
    }
    else if(iType>399 && iType<600)
    {
        //server-sent errors 400-599
        dbg_do(puts("IRC server sent errormsg"));
        char text[512];
        sprintf(text,"The IRC server sent the following error: #%d - \x22%s\x22",iType,ic.text.c_str());
        //***if(!sendClientMsg("",ymsg->username,text,false)) return 2;
        NwEMsg(NW_RESERVED_USERNAME,"",text);
    }
    else if(ic.type=="PING"){
        sendMsg(IrcCommand(username,"","PONG",ic.content));//*** check
        //****if(!sendIRC(IrcCommand(irc->nick,"","PONG",ic.content))) return 2; 
    }
    else if(iType==331 || iType==332)//TOPIC
    {
        NwEChanStart(stringtok(ic.subcontent," "), ic.text);
    }//333 owner?
    else if(iType==353)//NAMES
    {
		dbg_head("353");
        //:calvino.freenode.net 353 crash_daemonicus @ #test :crash_daemonicus +PizoteGreen +BigKing_3rd +orwell +freaksauce +shabble +MichealH +Gigs +dust--- Gryllida +abchirk +sysdef +jtrucks +kaffee @ChanServ +JStoker +Pira +xnt14 +Squeakers +Mkaysi +sdx23 +niekie +meder +auscompgeek +sash_ +tohirom_ +LuX
        NwEUsername(ic.destination);

		vector<string> tokens;
		/*StringTokenize(ic.subcontent,tokens," =*@");

		vector<string>::iterator it;
		for(it=tokens.begin();it!=tokens.end();it++)
		{
			dbgline("ROOM" + (*it));
		}*/

		string rm=stringtok(ic.subcontent," =*@");

		if(channel.name==rm)
		{
			tokens.clear();
			StringTokenize(ic.text,tokens," @+");

			//for(vector<string>::iterator it=tokens.begin();it!=tokens.end();it++)
			foreach_vector(string,it,tokens)
			{
				dbgline("User: "+(*it));
				NwEChanAppend(rm,*it);
			}
		}

        /*string rm=stringtok(ic.subcontent," =*@",false,true);
		dbgline(rm);
		dbgline(channel.name);
        if(channel.name==rm)
        {
            dbg_head("353 - splitting channel userlist");
            string del2=" @+";
            string tok=stringtok(ic.text,del2,false,true);
            while(tok!="")
            {
                dbgline(tok);
                NwEChanAppend(rm,tok);
                tok=stringtok_next();
            }
            dbg_foot(0);
        }
		dbg_foot(0);*/





    }else if(iType==366)
    {
        //:calvino.freenode.net 366 crash_daemonicus #test :End of /NAMES list
        //irc->channel  build a userlist and send to client;
        //mode 1: Arrive all of the users in 'irc->channel'
        //mode 2: collect a userlist and send to client
        if(channel.name==stringtok(ic.subcontent," "))
        {
            NwEChanEnd(channel.name);
            //***if(!sendClientUserlist(true)) return 2;
        }
    }
    else if(iType==375 || iType==372 || iType==376 || ic.type=="PRIVMSG" || ic.type=="NOTICE")
    {
        dbg_do(puts("PRIV / NOTICE recv'd - sending PM"));
        ic.shortorigin();
        NwEMsg(ic.origin,ic.destination,ic.text);
        //***if(!sendClientMsg(yorigin,dst,ic.text,chat)) return 2;
    }
    else if(ic.type=="NICK"){
        ic.shortorigin();
        
        string    newnick=ic.text;
		if(newnick=="") newnick=ic.destination;
        if(newnick=="") newnick=ic.content;
        
        NwEUsername(ic.origin,newnick);
        
        char text[512];
        sprintf(text,"'%s' is now known as '%s'",ic.origin.c_str(),newnick.c_str());//CHECK for NULLs
        //***if( !sendClientMsg("",ymsg->username,text,false)) return 2;
        NwEMsg(NW_RESERVED_USERNAME,username,text);
        
        //Leave the old nick just to keep it from clogging the client userlist.
        
        
        /*if(nickRefresh && !yours){
             ***
            YmsgField  fd(104,irc->channel.name);
            fd+=YmsgField(108,"1");
            fd+=YmsgField(109,ic.origin);
            fd+=YmsgField(110,"0");
            fd+=YmsgField(113,"1024");
            if( !sendYMSG(0x009B, fd) ) return 2;
            
            YmsgField  fd2(104,irc->channel.name);
            fd2+=YmsgField(105,"IRC Channel");
            fd2+=YmsgField(108,"1");
            fd2+=YmsgField(109,newnick);
            fd2+=YmsgField(110,"0");
            fd2+=YmsgField(113,"1024");
            if( !sendYMSG(0x0098, fd2) ) return 2;
        }*/
    }else if(ic.type=="JOIN"){
        ic.shortorigin();
        bool yours=(CheckOrigin(ic.origin)==NW_CHECK_USERNAME);
        
        string rm=ic.destination;
        if(rm=="" && ic.text!="") rm=ic.text;
        
        NwEChanAppend(rm,ic.origin);
        
        
        
        //if(yours) NwEChanStart(rm, "");//{
            //if( !sendClientUserlist(false) ) return 2;
            
            //*** if we can rely on 366 being sent, the above can be commented-out
            //the above (when paired with full u-list) makes some clients ignore the full u-list
            
            
            /*
            YmsgField  fd2(104,rm);
            fd2+=YmsgField(109,"YmsgIRC");
            fd2+=YmsgField(117,"Congratulations!  You now have access to chat rooms. [Notification: This IRC server might be recording IP addresses of users.]");
            fd2+=YmsgField(124,"1");
            if( !sendYMSG(0x00A8, &fd2) ) return 2;
        }else{
            
            YmsgField  fd(104,rm);
            fd+=YmsgField(105,"IRC Channel");
            fd+=YmsgField(108,"1");
            fd+=YmsgField(109,ic.origin);
            fd+=YmsgField(110,"0");
            fd+=YmsgField(113,"1024");
            
            if( !sendYMSG(0x0098, fd) ) return 2;
            
        }*/
        
    }else if(ic.type=="PART" || ic.type=="QUIT")
    {
        ic.shortorigin();
        //BUG: 'rm' value still null:
        //test-case:
            //:crash_demons_of_!~crash_dem@dialup-4.244.150.167.Dial1.StLouis1.Level3.net PART #test
        string rm=ic.destination;
        if(rm=="" && ic.text!="" && ic.type!="QUIT") rm=ic.text;
        if(rm=="") rm=channel.name;
        
        /*YmsgField  fd(104,rm);
        fd+=YmsgField(108,"1");
        fd+=YmsgField(109,yorigin);
        fd+=YmsgField(110,"0");
        fd+=YmsgField(113,"1024");
        if( !sendYMSG(0x009B, fd) ) return 2;*/
        NwEChanRemove(rm,ic.origin);
    }
    if(ic.type=="QUIT" || ic.type=="ERROR")
    {
        if(CheckOrigin(ic.origin)==NW_CHECK_USERNAME)
        {
            NwEDisconnect(false);
        }
    }
}
void IRC::stepActions()
{
    if(actions.size()<1) return;
    NwNotify n=actions.front();
    
    puts(name + ":ACTION:" + NwNotifyType_Debug(n.typ)
        +" "+n.sDat[0]+" "+n.sDat[1]+" "+n.sDat[2]
    );
    
    bool check_user=(n.cDat[0]==NW_CHECK_USERNAME);
    switch(n.typ)
    {
        case NW_DATA:
            sendMsg(IrcCommand(n.sDat[2]));
            break;
        case NW_LOGIN:
            //*** Login
            sendMsg(IrcCommand(username, "", "PASS", password, false));
            sendMsg(IrcCommand(username, "", "NICK", "" ,false, true));
            sendMsg(IrcCommand(username, "", "USER", "0 * :YmsgIrcUserJustTestingAClient",false, true));
            break;
        case NW_LOGOUT:
            //*** add logout packets...
            if(check_user)
            {
                sendMsg(IrcCommand("","","QUIT","Logging out",true));
            }
            NwEDisconnect();
            break;
        case NW_ULIST_APPEND:
            if(check_user)
            {
                sendMsg(IrcCommand("","","JOIN",n.sDat[0],false));
            }
            return NwNetwork::stepActions();
        case NW_ULIST_REMOVE:
            if(check_user)
            {
                sendMsg(IrcCommand("","","JOIN","0",false));
            }
            return NwNetwork::stepActions();
        case NW_PRIVMSG:
        case NW_CHATMSG:
            sendMsg(IrcCommand(n.sDat[0],n.sDat[1],"PRIVMSG",n.sDat[2],true));
			break;
        case NW_ULIST_START:
            sendMsg(IrcCommand("","","JOIN",n.sDat[0],false));
        case NW_ULIST_END:
        case NW_CONNINFO:
        case NW_CONNECT:
        case NW_DISCONNECT:
        case NW_PASSWORD:
        case NW_ULIST_TOPIC:
        case NW_ULIST_NAME:
        case NW_USERNAME:
        default:
            return NwNetwork::stepActions();
    }
    return actions.pop();
}

bool IRC::checkErrors(int nBytes)
{
    if(nBytes>=0 && connection.error==0) return false;
    if(connection.error==XSAEWOULDBLOCK) return false;// waiting for data
    dbgline(string("IRC Error: ")+itos(connection.error)+" "+itos(nBytes));
    NwEDisconnect(false);
    return true;
}

void IRC::stepRecv()
{
    int nBytes=0;
    if(connection.sock==INVALID_SOCKET)
    {
        //not connected
        connection.iReset();
    }else{
        nBytes=connection.get(1);
        if(checkErrors(nBytes)) return;
        if(nBytes > 0)
        {
            int i=connection.iLastWritten();
            if(i==connection.ixBuf || connection.buf[i]==0x0A)//last byte or LineFeed.
            {
                connection.iReset();//command completed, next command needs to begin at front of buffer.
                connection.buf[i]=0;//null the Linefeed
                if(i>0 && connection.buf[i-1]==0x0D) connection.buf[i-1]=0;//null the preceding Carriage Return
                //buffer is now a C-string containing the IRC command
                
                IrcCommand ic(connection.buf);
                puts(string("<<< IRC: ")+ic.str + "\r\n");
                stepIncoming(ic);
                if(checkErrors(nBytes)) return;
            }
        }
    }
}
void IRC::step(NwStepState state)
{
    switch(state)
    {
        case NW_STEP_BEGIN:
            break;
        case NW_STEP_END:
            connection.close();
            break;
        case NW_STEP:
            stepActions();
            do{
                stepRecv();
            }while(connection.error==0 && connection.sock!=INVALID_SOCKET);
            break;
    }
}
//void IRC::notifyStateText(const string& state){;}
